/*
 * TMap Designer - A Thematic Map design software
 * Copyright (C) 2006 World Health Organization
 * http://www.who.int
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package who.tmap.dao.impl;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;

import who.tmap.bo.AttrDef;
import who.tmap.bo.Attrs;
import who.tmap.bo.BizObject;
import who.tmap.bo.impl.BizObjectFactory;
import who.tmap.common.TMapException;
import who.tmap.dao.DataDAO;
import who.tmap.dao.FileExtensions;
import who.tmap.dao.TMapDAOException;
import who.tmap.exp.CSVExportAdapter;
import who.tmap.exp.DBaseExportAdapter;
import who.tmap.exp.ExcelExportAdapter;
import who.tmap.imp.CSVImportAdapter;
import who.tmap.imp.ExcelImportAdapter;
import who.tmap.imp.ImportAdapter;
import who.tmap.imp.ImportAdapters;
import who.tmap.imp.ODBCDSNLessImportAdapter;
import who.tmap.imp.dbase.DBaseFieldDescriptor;
import who.tmap.imp.dbase.DBaseFile;
import who.tmap.imp.dbase.DBaseFileReader;
import who.tmap.imp.impl.ImportAdapterFactory;
import who.tmap.toolkit.impl.AttrHelper;

/**
 * @author stl
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class DataDAOImpl extends BaseDAOImpl 
						  implements DataDAO {
 
    private static final String VERSION ="1.0";    
    
    /**
     *	Package visible only (enough for the factory)
     */
    DataDAOImpl() {  
        super();
    }
    
    @SuppressWarnings("unchecked")
	public BizObject loadXml( Element elt ) throws TMapDAOException {
    	Attrs attrs =null;
        
        if ( !isLinkedFile( elt ) ) {
	        AttrHelper attrHelper =new AttrHelper();
	
	        try {
	            attrs =(Attrs)bof.get( BizObjectFactory.ATTRS );            
	        
		        String version =elt.getAttributeValue("version");            
		        
		        // read header info
		        List<AttrDef> header =new ArrayList<AttrDef>();
		        attrs.setHeader( header );
		        
		        Element headerElt =elt.getChild( "Header" );
		        
		        Iterator it =headerElt.getChildren().iterator();
		        while( it.hasNext() ) {
		            Element attrDefElt =(Element)it.next();
		            
		            AttrDef attrDef =(AttrDef)bof.get( BizObjectFactory.ATTR_DEF );	            
		            attrDef.setName( attrDefElt.getAttributeValue( "name" ) );
		            attrDef.setType( attrDefElt.getAttributeValue( "type" ) );
		            
		            header.add( attrDef );
		        }
		        
		        // read rows
		        List<List> rows =new ArrayList<List>();
		        attrs.setRows( rows );
		        
		        Element rowsElt =elt.getChild( "Rows" );
		        Object value;
		        
		        // for every row
		        it =rowsElt.getChildren().iterator();
		        while( it.hasNext() ) {
		            Element rowElt =(Element)it.next();
		            
		            List<Object> row =new ArrayList<Object>();
		            rows.add( row );
		            
		            // for every attr
		            int attrCt =0;
		            Iterator it2 =rowElt.getChildren().iterator();
		            while( it2.hasNext() && attrCt < header.size() ) {                
		                Element attrElt =(Element)it2.next();
		                
		                AttrDef attrDef =(AttrDef)header.get( attrCt++ );
		                String attrType =( attrDef != null ) ? attrDef.getType() : "";
		                
		                try {
		                    value =attrHelper.getValueFromString( attrElt.getText(), attrType );
		                } catch( Exception e ) {
		                    e.printStackTrace();
		                    
		                    // This entry does not match column type
		                    value =attrElt.getText();
		                }
		                
		                if ( "".equals( attrElt.getText() ) && value instanceof Number ) {
		                	value =null;
		                }
		                                
		                row.add( value );
		            } 
		        }

		        attrs.setDsProperties( loadDsProperties( elt ) ); 
		        		        
	        } catch( Exception e ) {
	            throw new TMapDAOException( e );
	        }
        } else {
    	    Attribute linkElt =elt.getAttribute( "linkFile" ); 
    	    String fileName =linkElt.getValue();
    	    if ( fileName.toUpperCase().endsWith( FileExtensions.DATA_EXT.toUpperCase() ) ) {
    	    	attrs =(Attrs)loadLinkedFile( elt );
    	    } else {
    	    	try {
    	    		Map<String, String> dsProperties =loadDsProperties( elt );
    	    		
    	    		String dsType =dsProperties.get( ImportAdapter.DS_TYPE );
    	    		
    	    		if ( dsType.equalsIgnoreCase( FileExtensions.DBF_EXT ) ) {
    	    			attrs =loadDbf( fileName );
    	    			
    	    		} else {
	    	    		int adapterType =-1;
	    	    		
	    				if ( dsType.equals( ExcelImportAdapter.class.getName() ) ) {
	    					adapterType =ImportAdapters.EXCEL;
	    				} else if ( dsType.equals( CSVImportAdapter.class.getName() ) ) {
	    					adapterType =ImportAdapters.CSV;
	    				} else if ( dsType.equals( ODBCDSNLessImportAdapter.class.getName() ) ) {
	    					adapterType =ImportAdapters.JDBC_ODBC;
	    				}
	    	    		
						// use properties to import the linked file
						ImportAdapter adapter =ImportAdapterFactory.getInstance()
																   .getImportAdapter( adapterType  );
						
						adapter.configure( dsProperties );	
						
						adapter.connect();
						
						try {
						    attrs =adapter.readValues();
						   
						} catch( Exception e ) {
						    throw new TMapException( e );
						} finally {
						    if ( adapter != null ) {
						        adapter.disconnect();
						    }						    
						}
						
	    			}
    	    		
				    attrs.setDsProperties( dsProperties );
			        attrs.setFile( new File( fileName ) );
			        
				} catch (Exception e) {
					throw new TMapDAOException( e );
				}
    	   
    	    }

        }
        
        return attrs;    
    }
    
    @SuppressWarnings("unchecked")
	private Map<String, String> loadDsProperties( Element elt ) {
        // read data source properties
        Element dspsElt =elt.getChild( "DataSourceProperties" );
        
        Map<String, String> dspProperties =new TreeMap<String, String>();
                
        // for every data source properties
        if ( dspsElt != null ) {
	        Iterator it =dspsElt.getChildren().iterator();
	        while( it.hasNext() ) {
	            Element dspElt =(Element)it.next();
	            
	            dspProperties.put( dspElt.getAttributeValue("name"), dspElt.getAttributeValue("val") );
	        }
        }
        
        return dspProperties;
    }

    /**
     * Load a set of attributes from a TMap xml file
     */
    public BizObject load( String fileName ) throws TMapDAOException {
    	BizObject object =null;
    	
    	try {
    		Element rootElt =getRootElement( fileName );
    		object =loadXml( rootElt );
    		object.setFile( new File( fileName ) );
    	} catch( Exception e ) {
    		throw new TMapDAOException( e );
    	}
    	
    	return object;
    }    
    
    @SuppressWarnings("unchecked")
	public Attrs loadDbf( String fileName ) throws TMapException {
    	DBaseFileReader dbaseHelper =new DBaseFileReader();
    	DBaseFile dbaseFile =dbaseHelper.readDBaseFile( new File( fileName ) );
    	
        Attrs attrs =null;
        AttrHelper attrHelper =new AttrHelper();

        try {
            attrs =(Attrs)bof.get( BizObjectFactory.ATTRS );            
                
	        // read header info
	        List<AttrDef> header =new ArrayList<AttrDef>();
	        attrs.setHeader( header );
	        
	        DBaseFieldDescriptor[] fieldsDesc =dbaseFile.getFieldsDescriptor();
	        for( int ct =0; ct < fieldsDesc.length; ct++ ) {
	            AttrDef attrDef =(AttrDef)bof.get( BizObjectFactory.ATTR_DEF );	            
	            attrDef.setName( fieldsDesc[ ct ].getName() );
	            attrDef.setType( fieldsDesc[ ct ].getType() );
	            
	            header.add( attrDef );
	
	        }
	        
	        List<List> rows =new ArrayList<List>();
	        attrs.setRows( rows );	        
	        
	        Object[] records =dbaseFile.getRecords();
	        
	        // for every row	        
	        for( int ct =0; ct < records.length; ct++ ) {
	        	Object[] record =(Object[])records[ ct ];
	        	
	            List<Object> row =new ArrayList<Object>();
	            rows.add( row );

	            // for every field of the record
	            for( int fieldCt =0; fieldCt < record.length; fieldCt++ ) {
	            	row.add( record[ fieldCt ] );
	            	
	            	// Define java types for header types using the first record
	            	if ( ct == 0 && record[ fieldCt ] != null ) {
	            		AttrDef attrDef =(AttrDef)header.get( fieldCt );
	            		
	            		attrDef.setType( record[ fieldCt ].getClass().getName() );
	            	}
	            }
	        }
	        
        } catch( Exception e ) {
            throw new TMapDAOException( e );
        }
        
        return attrs;    	

    }

	@SuppressWarnings("unchecked")
	public Element saveXml(BizObject object) throws TMapDAOException {
        Attrs attrs =(Attrs)object;        
        XMLOutputter outputter =getXMLOutputter();
        
        Element rootElt =new Element( ROOT_ELT );
        rootElt.setAttribute( "version", VERSION );        
        
        // Build header node
        Element headerElt =new Element( "Header" );
        rootElt.addContent( headerElt );
        
        if ( attrs != null ) {
	        List<AttrDef> header =(List<AttrDef>)attrs.getHeader();
	        for( AttrDef attrDef : header ) {	            
	            Element attrDefElt =new Element( "Def" );
	            headerElt.addContent( attrDefElt );
	            
	            attrDefElt.setAttribute( "name", outputter.escapeAttributeEntities( attrDef.getName() ) );
	            attrDefElt.setAttribute( "type", outputter.escapeAttributeEntities( attrDef.getType() ) );   
	        }
        }
	        
        // Build rows node
        Element rowsElt =new Element( "Rows" );
        rootElt.addContent( rowsElt );

        if ( attrs != null ) {
	        Iterator it =attrs.getRows().iterator();
	        while( it.hasNext() ) {
	            List row =(List)it.next();
	            
	            Element rowElt =new Element( "Row" );
	            rowsElt.addContent( rowElt );
	            
	            Iterator it2 =row.iterator();
	            while( it2.hasNext() ) {
	                Object value =(Object)it2.next();
	                
	                Element attrElt =new Element( "Val" );
	                rowElt.addContent( attrElt );
	                
	                if ( value == null ) {
	                    value ="";
	                }
	                
	                attrElt.addContent( outputter.escapeElementEntities( value.toString() ) );
	            }
	        }
        }
        
        // Build Data Source Properties
        saveDsProperties( rootElt, attrs );
        
        return rootElt;
	}
	
	public void saveDsProperties( Element rootElt, BizObject object ) {
        Attrs attrs =(Attrs)object;  
        
        if ( attrs.getDsProperties() != null && attrs.getDsProperties().size() > 0 ) {
        	Element dsProperties =new Element( "DataSourceProperties" );
        	rootElt.addContent( dsProperties );
        	
        	for( String name : attrs.getDsProperties().keySet() ) {
        		String val =attrs.getDsProperties().get( name );
        		
        		Element dspEntry =new Element( "Property" );
        		dsProperties.addContent( dspEntry );
        		
        		dspEntry.setAttribute( "name", name );
        		dspEntry.setAttribute( "val", val );        		
        	}
        }
	}
	
	public void saveToExcel( String fileName, BizObject object ) throws TMapDAOException {
		try {
			new ExcelExportAdapter().export( new File( fileName ), (Attrs)object );
        } catch( Exception e ) {
        	throw new TMapDAOException( e );        	
        }			
	}
	
	public void saveToCSV( String fileName, BizObject object ) throws TMapDAOException {
		try {
			new CSVExportAdapter().export( new File( fileName ), (Attrs)object );
        } catch( Exception e ) {
        	throw new TMapDAOException( e );        	
        }		
	}	
	
	public void saveToDBF( String fileName, BizObject object ) throws TMapDAOException {		
        try {
        	new DBaseExportAdapter().export( new File( fileName ), (Attrs)object );
        } catch( Exception e ) {
        	throw new TMapDAOException( e );        	
        }
	}

}
